package com.hyzs.service.impl;


import com.google.common.base.Function;
import com.google.common.base.Functions;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicates;
import com.google.common.collect.*;
import com.hyzs.service.FulltextService;
import com.hyzs.service.GraphService;
import com.hyzs.utils.LabelKeyWords;
import com.hyzs.utils.Neo4jConnectionUtils;
import com.hyzs.utils.Neo4jFunctionUtils;
import com.hyzs.utils.StringUtils;
import org.apache.commons.lang3.RandomStringUtils;
import org.neo4j.driver.v1.Driver;
import org.neo4j.driver.v1.Record;
import org.neo4j.driver.v1.Session;
import org.neo4j.driver.v1.StatementResult;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

@Service
@Qualifier("neo4JService")
public class Neo4JService implements GraphService, FulltextService {


    private static final Logger logger = LoggerFactory.getLogger(Neo4JService.class);
    private static Map<String, Object> MAX_SCORE_MAP;

    @Autowired
    private Neo4jConnectionUtils neo4jConnectionUtils;


    @Override
    public List<Map<String,Object>> getSomeData(String limit) {
        Session session = neo4jConnectionUtils.getSession();
        String sql = " MATCH (p) -[r]-> (l) RETURN p,r,l LIMIT " + limit;
        StatementResult statementResult = session.run(sql);
        List<Map<String, Object>> resList =  statementResult.stream()
                .map(record -> {
                    Map<String, Object> resMap = new HashMap<>();
                    resMap.put("from", record.get("p").asNode().asMap());
                    resMap.put("rel", record.get("r").asRelationship().type());
                    resMap.put("to", record.get("l").asNode().asMap());
                    return resMap;
                })
                .collect(Collectors.toList());
        session.closeAsync();
        return resList;
    }


    @Override
    public List<Map<String, Object>> getLawByName(String name, String limit) {
         
         Session session = neo4jConnectionUtils.getSession();
        String sql = " MATCH (l:Law) " +
                " WHERE l.name =~ '.*" + name + ".*' " +
                " RETURN l LIMIT " + limit;
        StatementResult statementResult = session.run(sql);
        List<Map<String, Object>> resList =  statementResult.stream()
                .map(record -> {
                    Map<String, Object> resMap = new HashMap<>();
                    Map<String, Object> lawMap = new HashMap<>();
                    lawMap.putAll(record.get("l").asNode().asMap());
                    StringUtils.updateLawMap(lawMap);
                    resMap.put("law", lawMap);
                    return resMap;
                }).collect(Collectors.toList());
        session.closeAsync();
        return resList;

    }

    @Override
    public List<Map<String, Object>> getSimJudgements(String indictmentContent, String topK) {
         
         Session session = neo4jConnectionUtils.getSession();
        List<String> lawList = StringUtils.findBaseLawList(StringUtils.trimBlank(indictmentContent));
        Preconditions.checkArgument(lawList.size() > 0, "No law infos extract! This content is NOT applicable for this api. ");
        String sql = " UNWIND $lawList as lawName\n" +
                " MATCH (l:Law {name: lawName})\n" +
                " WITH {item:9999999, categories: collect(id(l))} as startTarget\n" +
                " MATCH (p:Judgement)-[:BASED_ON]->(law:Law{level: 2}) \n" +
                " WITH startTarget, {item:id(p), categories: collect(id(law))} as userData\n" +
                " WITH startTarget, (collect(userData)+ startTarget) as totalData\n" +
                " CALL algo.similarity.jaccard.stream(totalData, {sourceIds: [startTarget.item], topK:$topK})\n" +
                " YIELD item1, item2, intersection, similarity\n" +
                " RETURN item1, algo.asNode(item2) AS item2, intersection,similarity ";
        int k =10;
        try {
            k = Integer.parseInt(topK);
        } catch (NumberFormatException e) {
            System.err.println(" topK format error !");
        }
        StatementResult statementResult = session.run(sql,
                ImmutableMap.of("lawList", lawList, "topK", k));
        List<Map<String, Object>> resList =  statementResult.stream()
                .map( record -> {
                    Map<String, Object> resMap = new HashMap<>();
                    Map<String, Object> judgeMap = new HashMap<>();
                    // update date string to timestamp
                    judgeMap.putAll(record.get("item2").asNode().asMap());
                    StringUtils.updateJudgementMap(judgeMap);
                    resMap.put("Judgement", judgeMap);
                    resMap.put("similarity", record.get("similarity").toString());
                    return resMap;
                })
                .collect(Collectors.toList());
        session.closeAsync();
        return resList;
    }

    @Override
    public List<Map<String, Object>> getJudgementLaws(String indictmentContent){
         
         Session session = neo4jConnectionUtils.getSession();
        List<String> rawList = StringUtils.findBaseLawList(StringUtils.trimBlank(indictmentContent));
        List<String> lawList =  ImmutableSet.copyOf(Iterables.filter(rawList, Predicates.notNull())).asList();
        String sql = " UNWIND $lawList as lawName " +
                " MATCH (l:Law {name:lawName}) " +
                " RETURN l";
        StatementResult statementResult = session.run(sql, ImmutableMap.of("lawList", lawList));
        List<Map<String, Object>> resList =  statementResult.stream()
                .map( record -> {
                    Map<String, Object> resMap = new HashMap<>();
                    Map<String, Object> lawMap = new HashMap<>();
                    lawMap.putAll(record.get("l").asNode().asMap());
                    StringUtils.updateLawMap(lawMap);
                    resMap.put("law", lawMap);
                    return resMap;
                })
                .collect(Collectors.toList());
        session.closeAsync();
        return resList;
    }

    @Override
    public Map<String, Object> getJudgementContentById(String doc_id) {
         
         Session session = neo4jConnectionUtils.getSession();
        String sql = " MATCH (c:Content{doc_id: $doc_id}) RETURN c";
        Map<String, Object> resMap =  session.run(sql, ImmutableMap.of("doc_id", doc_id))
                .next()
                .get("c").asNode().asMap();
        session.close();
        return resMap;
    }

    @Override
    public List<Map<String, Object>> getJudgementContentByIds(List<String> doc_ids) {
         
         Session session = neo4jConnectionUtils.getSession();
        String sql = " MATCH (c:Content) WHERE c.doc_id in $doc_ids RETURN c";
        List<Map<String, Object>> resList =  session.run(sql, ImmutableMap.of("doc_ids", doc_ids))
                .stream()
                .map(record -> record.get("c").asNode().asMap())
                .collect(Collectors.toList());
        session.closeAsync();
        return resList;
    }

    @Override
    public List<Map<String, Object>> getRelativeLaws(String names, String topK) {
         
         Session session = neo4jConnectionUtils.getSession();

        String[] nameList = names.split(",");
        String sql = " MATCH (p1:Law) WHERE p1.name in $names WITH collect(p1) AS sourceLaws " +
                "CALL algo.pageRank.stream(" +
                " 'MATCH (p) WHERE p:Law or p:Judgement RETURN id(p) AS id'," +
                " 'MATCH (p1) -[:BASED_ON|:BELONGS]- (p2) RETURN id(p1) AS source, id(p2) AS target', " +
                " {sourceNodes:sourceLaws, graph: 'cypher', direction:'BOTH', iterations:6, dampingFactor:0.75, concurrency: 4}) " +
                " YIELD nodeId, score " +
                " WITH algo.asNode(nodeId) AS n, score " +
                " MATCH (n:Law) " +
                " WHERE NOT n.name IN $names " +
                " RETURN n, score " +
                " ORDER BY score DESC " +
                " LIMIT $topK";
        Map<String, Object> params = new HashMap<>();
        params.put("names", nameList);
        int k =10;
        try {
            k = Integer.parseInt(topK);
        } catch (NumberFormatException e) {
            System.err.println(" topK format error !");
        }
        params.put("topK", k);
        StatementResult statementResult = session.run(sql, params);
        List<Map<String, Object>> resList =  statementResult.stream()
                .map(record -> {
                    Map<String, Object> resMap = new HashMap<>();
                    Map<String, Object> lawMap = new HashMap<>();
                    lawMap.putAll(record.get("n").asNode().asMap());
                    StringUtils.updateLawMap(lawMap);
                    resMap.put("relativeLaw", lawMap);
//                    resMap.put("relativeLaw", record.get("n").asNode().asMap());
                    resMap.put("score", record.get("score").toString());
                    return resMap;
                })
                .collect(Collectors.toList());
        session.closeAsync();
        return resList;
    }

    @Override
    public List<Map<String, Object>> getJudgementByLaw(String name, String limit) {
         
         Session session = neo4jConnectionUtils.getSession();
        String sql = " MATCH (j:Judgement) -- (l:Law) " +
                " WHERE l.name =~ '.*" + name + ".*' " +
                " RETURN j LIMIT " + limit;
        StatementResult statementResult = session.run(sql);
        List<Map<String, Object>> resList =  statementResult.stream()
                .map(record -> {
                    Map<String, Object> resMap = new HashMap<>();
                    resMap.put("judgement", record.get("j").asNode().asMap());
                    return resMap;
                })
                .collect(Collectors.toList());
        session.closeAsync();
        return resList;
    }

    @Override
    public List<Map<String, Object>> getJudgementByTitle(String name, String limit) {
         
         Session session = neo4jConnectionUtils.getSession();
        String sql = " MATCH (j:Judgement) " +
                " WHERE j.title =~ '.*" + name + ".*' " +
                " RETURN j LIMIT " + limit;
        StatementResult statementResult = session.run(sql);
        List<Map<String, Object>> resList =  statementResult.stream()
                .map(record -> {
                    Map<String, Object> resMap = new HashMap<>();
                    resMap.put("judgement", record.get("j").asNode().asMap());
                    return resMap;
                })
                .collect(Collectors.toList());
        session.closeAsync();
        return resList;
    }

    // update Judgement.id => Judgement.doc_id
    @Override
    public List<Map<String, Object>> getRelativeCasesByIds(String ids, String topK) {
         
         Session session = neo4jConnectionUtils.getSession();
        String[] idList = ids.split(",");
        String sql = " MATCH (p:Judgement)-[:BASED_ON]->(law:Law{level: 2}) " +
                "WITH {item:id(p), doc_id:p.doc_id, categories: collect(id(law))} as userData " +
                "WITH collect(userData) as data " +
                "WITH data, [value in data WHERE value.doc_id in $idList | value.item] AS sourceIds " +
                "CALL algo.similarity.jaccard.stream(data, {sourceIds: sourceIds, topK: $topK}) " +
                "YIELD item1, item2, count1, count2, intersection, similarity " +
                "RETURN  algo.asNode(item1) AS item1, algo.asNode(item2) AS item2, intersection, similarity";
        Map<String, Object> params = new HashMap<>();
        params.put("idList", idList);
        params.put("topK", Integer.parseInt(topK));
        StatementResult statementResult = session.run(sql, params);
        List<Map<String, Object>> resList =  statementResult.stream()
                .map(record -> {
                    Map<String, Object> resMap = new HashMap<>();
                    resMap.put("item1", record.get("item1").asNode().asMap());
                    resMap.put("item2", record.get("item2").asNode().asMap());
                    resMap.put("intersection", record.get("intersection").toString());
                    resMap.put("similarity", record.get("similarity").toString());
                    return resMap;
                })
                .collect(Collectors.toList());
        session.closeAsync();
        return resList;
    }

    @Override
    public List<Map<String, Object>> getRelativeCasesByTitle(String title, String topK) {
         
         Session session = neo4jConnectionUtils.getSession();
        String sql = " MATCH (p1:Judgement) " +
                "WHERE p1.title =~ '.*"+title+".*' " +
                "WITH p1 " +
                "MATCH (p1) --(relatives1:Law) " +
                "WITH p1, collect(id(relatives1)) AS p1relatives " +
                "MATCH (p2:Judgement)--(relatives2:Law) WHERE p1 <> p2 " +
                "WITH p1, p1relatives, p2, collect(id(relatives2)) AS p2relatives " +
                "RETURN p1, p2, algo.similarity.jaccard(p1relatives,p2relatives) AS similarity " +
                "ORDER BY similarity DESC LIMIT "+ topK;
        StatementResult statementResult = session.run(sql);
        List<Map<String, Object>> resList =  statementResult.stream()
                .map(record -> {
                    Map<String, Object> resMap = new HashMap<>();
                    resMap.put("item1", record.get("p1").asNode().asMap());
                    resMap.put("item2", record.get("p2").asNode().asMap());
                    resMap.put("similarity", record.get("similarity").toString());
                    return resMap;
                })
                .collect(Collectors.toList());
        session.closeAsync();
        return resList;
    }


    @Override
    public Map<String, Object> getLawTree(String name) {
         
         Session session = neo4jConnectionUtils.getSession();
        String sql = "  MATCH (n:Law{name: $name}) return n, n.level as level ";
        StatementResult statementResult = session.run(sql, ImmutableMap.of("name", name));
        Record record = statementResult.next();
        Map<String, Object> resMap =  Neo4jFunctionUtils.generateLawMap(session, record);
        session.closeAsync();
        return resMap;
    }


    public List<Object> queryContentLabels(String baseContent) {
         
         Session session = neo4jConnectionUtils.getSession();
        final String randomId = RandomStringUtils.randomAlphabetic(12);
        Map<String,Object> prop = ImmutableMap.of("doc_id", randomId,
                "content", StringUtils.trimJudgementContent(StringUtils.trimBlank(baseContent)),
                "tmp", "y");
        Neo4jFunctionUtils.addLabelContent(session, prop);
        ConcurrentMap<String, Double> scoreMap = new ConcurrentHashMap<>();
        LabelKeyWords.isAllLabels.forEach((label, keyWord) -> {
            Double score = Neo4jFunctionUtils.queryFulltextContent(session, keyWord, randomId);
            scoreMap.put(label, score);
        });
        Neo4jFunctionUtils.deleteLabelContent(session, randomId);
        session.closeAsync();

//        TreeMap<String,Object> sortedMap = new TreeMap<>(new ValueComparator(scoreMap));
//        sortedMap.putAll(scoreMap);

        return scoreMap.entrySet()
                .stream()
                .sorted(Comparator.comparing(Map.Entry::getValue, Comparator.reverseOrder()))
                .limit(10)
                .map(entry -> Arrays.asList(entry.getKey(), entry.getValue()))
                .collect(Collectors.toList());
    }

    // available crimeNames :
    // WXJS_behavior
    // JTZS_behavior
    // ZSFMYSZZDP_circums, ZSFMYSZZDP_behavior
    // XXZS_circums, XXZS_harm, XXZS_behavior
    // penalty_heavier, penalty_ease, penalty_ease_circumstance

    @Override
    public Map<String,Object> availableLabels() {
        return ImmutableMap.of("labels", ImmutableList.copyOf(LabelKeyWords.isAllLabels.keySet()));
    }

    @Override
    public Map<String,Object> queryContentLabels(String baseContent, String crimeName, int k){
        try(Session session = neo4jConnectionUtils.getSession()){
            final String randomId = RandomStringUtils.randomAlphabetic(12);
            Map<String,Object> prop = ImmutableMap.of("doc_id", randomId,
                    "content", StringUtils.trimJudgementContent(StringUtils.trimBlank(baseContent)),
                    "tmp", "y");
            Neo4jFunctionUtils.addLabelContent(session, prop);
            Preconditions.checkArgument(
                    crimeName.split("_").length >=2, "bad format query label string!");

            List<Map<String,Object>> resList = Neo4jFunctionUtils.genLabelList(session, randomId, crimeName, k);

            Neo4jFunctionUtils.deleteLabelContent(session, randomId);
            session.closeAsync();

            return ImmutableMap.of("data", resList);
        }

    }

    @Override
    public Map<String, Object> queryContentLabelByCrimes(String baseContent, List<String> crimes) {
        if(MAX_SCORE_MAP == null)
            MAX_SCORE_MAP = genMaxScoreMaps();
        try(Session session = neo4jConnectionUtils.getSession()){
            final String randomId = RandomStringUtils.randomAlphabetic(12);
            Map<String,Object> prop = ImmutableMap.of("doc_id", randomId,
                    "content", StringUtils.trimJudgementContent(StringUtils.trimBlank(baseContent)),
                    "tmp", "y");
            Neo4jFunctionUtils.addLabelContent(session, prop);

            List<Map<String,Object>> resList = Neo4jFunctionUtils.genLabelListByCrimes(session, randomId, MAX_SCORE_MAP, crimes);
            Neo4jFunctionUtils.deleteLabelContent(session, randomId);
            session.closeAsync();

            return ImmutableMap.of("data", resList);
        }

    }

    @Override
    public Map<String, Object> gJQueryContentLabels(String baseContent, List<String> crimeArray) {

        try(Session session = neo4jConnectionUtils.getSession()) {
            final String randomId = RandomStringUtils.randomAlphabetic(12);
            Map<String, Object> prop = ImmutableMap.of("doc_id", randomId,
                    "content", StringUtils.trimJudgementContent(StringUtils.trimBlank(baseContent)),
                    "tmp", "y");
            Neo4jFunctionUtils.addLabelContent(session, prop);

            List<Object> resList = crimeArray.stream()
                    .map(crime -> Neo4jFunctionUtils.genQueryLabelCode(session, randomId, crime))
                    .collect(Collectors.toList());

            Neo4jFunctionUtils.deleteLabelContent(session, randomId);

            session.closeAsync();

            return ImmutableMap.of("data", resList);
        }
    }


    public Map<String, Object> genMaxScoreMaps(){
        Session session = neo4jConnectionUtils.getSession();
        Map<String,Object> resMap = LabelKeyWords.isAllLabels.entrySet().stream()
                .collect(Collectors.toMap(Map.Entry::getKey, e -> Neo4jFunctionUtils.queryMaxScore(session, e.getValue())));
        session.closeAsync();
        return resMap;

    }







}
